#region  == Copyright == 
// =====================================================================
//  Microsoft Consulting Services France - Aymeric Mouillé - 2013
//  Projet     : CRM Solution Import Status
//  Fichier    : MainForm.cs (22/03/2013 16:55)
//  
//  Copyright (C) Microsoft Corporation.  All rights reserved.
// =====================================================================
#endregion

namespace CRM.Solution.Import.Status
{
    #region  == Using == 
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Threading;
    using System.Windows.Forms;
    using System.Xml;

    using CRM.Solution.Import.Status.Properties;

    using Microsoft.Xrm.Client;
    using Microsoft.Xrm.Client.Services;
    using Microsoft.Xrm.Sdk;
    using Microsoft.Xrm.Sdk.Query;

    #endregion

    public partial class MainForm : Form
    {
        private JobItem CurrentJob;

        private List<JobItem> JobItems;

        private IOrganizationService OrganizationService;

        private readonly BackgroundWorker BgWorkerCurrentJob;

        private readonly BackgroundWorker BgWorkerCurrentJobProgress;

        private readonly BackgroundWorker BgWorkerJobs;

        private delegate void UpdateStatusDelegate(string value, int progress);

        #region = Constructor =
        /// <summary>
        /// Initializes a new instance of the <see cref="MainForm" /> class.
        /// </summary>
        public MainForm()
        {
            InitializeComponent();

            BgWorkerCurrentJob = new BackgroundWorker();
            BgWorkerCurrentJobProgress = new BackgroundWorker();
            BgWorkerJobs = new BackgroundWorker();

            txtOrgUrl.Text = Settings.Default.OrgUrl;
            txtDomain.Text = Settings.Default.Domain;
            txtLogin.Text = Settings.Default.Login;
            txtPassword.Text = Settings.Default.Password;
            txtCheckInterval.Text = Settings.Default.CheckInterval.ToString();

            progressBarJob.Minimum = 0;
            progressBarJob.Maximum = 100;
        }
        #endregion

        #region Get Crm Connection String
        /// <summary>
        /// Gets the CRM connection string.
        /// </summary>
        /// <returns></returns>
        private string GetCrmConnectionString()
        {
            if (string.IsNullOrEmpty(txtOrgUrl.Text))
            {
                MessageBox.Show(Resources.MainForm_GetCrmConnectionString_You_need_do_specify__organization_url__in_settings_tab);
                return string.Empty;
            }

            if (string.IsNullOrEmpty(txtDomain.Text))
            {
                MessageBox.Show(Resources.MainForm_GetCrmConnectionString_You_need_do_specify__domain__in_settings_tab);
                return string.Empty;
            }

            if (string.IsNullOrEmpty(txtLogin.Text))
            {
                MessageBox.Show(Resources.MainForm_GetCrmConnectionString_You_need_do_specify__login__in_settings_tab);
                return string.Empty;
            }

            if (string.IsNullOrEmpty(txtPassword.Text))
            {
                MessageBox.Show(Resources.MainForm_GetCrmConnectionString_You_need_do_specify__password__in_settings_tab);
                return string.Empty;
            }

            string crmConnectionString = string.Format("Url={0}; Domain={1}; Username={2}; Password={3}", txtOrgUrl.Text, txtDomain.Text, txtLogin.Text, txtPassword.Text);
            return crmConnectionString;
        }
        #endregion

        #region Get Status
        /// <summary>
        /// Gets the status.
        /// </summary>
        /// <param name="dataXml">The data XML.</param>
        /// <returns></returns>
        private string GetStatus(string dataXml)
        {
            string status = string.Empty;
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(dataXml);

            try
            {
                var node = xmlDoc.SelectSingleNode("importexportxml");
                if (node != null)
                {
                    if (node.Attributes != null
                        && node.Attributes["status"] != null)
                    {
                        status = node.Attributes["status"].Value;
                    }
                }

                if (string.IsNullOrEmpty(status))
                {
                    node = xmlDoc.SelectSingleNode("importexportxml/solutionManifests/solutionManifest/result");
                    if (node != null)
                    {
                        if (node.Attributes != null
                            && node.Attributes["result"] != null)
                        {
                            status = node.Attributes["result"].Value;
                            if (!string.IsNullOrEmpty(node.Attributes["errortext"].Value))
                            {
                                status += string.Format("  ({0})", node.Attributes["errortext"].Value);
                            }
                        }
                    }
                }
            }
            catch (Exception)
            {
                // ignore
            }
            return status;
        }
        #endregion

        #region Tab Current Job Enter
        /// <summary>
        /// Handles the Enter event of the tabCurrentJob control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="EventArgs" /> instance containing the event data.
        /// </param>
        private void tabCurrentJob_Enter(object sender, EventArgs e)
        {
            BgWorkerCurrentJob.DoWork += bgWorkerCurrentJob_DoWork;
            BgWorkerCurrentJob.RunWorkerCompleted += bgWorkerCurrentJob_RunWorkerCompleted;
            if (!BgWorkerCurrentJob.IsBusy)
            {
                UpdateStripStatus("Loading, please wait ...");
                BgWorkerCurrentJob.RunWorkerAsync();
            }
        }
        #endregion

        #region Load Current Import Job
        /// <summary>
        /// Loads the current import job.
        /// </summary>
        private void LoadCurrentImportJob()
        {
            string crmConnectionString = GetCrmConnectionString();
            if (string.IsNullOrEmpty(crmConnectionString))
            {
                return;
            }

            UpdateStripStatus("Connecting ...");
            var connection = CrmConnection.Parse(crmConnectionString);
            connection.Timeout = new TimeSpan(0, 5, 0);
            OrganizationService = new OrganizationService(connection);

            UpdateStripStatus("Querying for import job ...");
            FilterExpression filter = new FilterExpression();
            filter.AddCondition(new ConditionExpression("completedon", ConditionOperator.Null));
            var query = new QueryExpression("importjob") { ColumnSet = new ColumnSet(true), Criteria = filter };

            List<JobItem> jobItems = new List<JobItem>();
            try
            {
                var response = OrganizationService.RetrieveMultiple(query);
                UpdateStripStatus("Processing results ...");
                foreach (var importJob in response.Entities)
                {
                    var jobItem = new JobItem { JobId = importJob.Id, Name = importJob["name"].ToString(), SolutionName = importJob["solutionname"].ToString(), CreatedOn = importJob["startedon"].ToString(), };

                    if (importJob.Attributes.Contains("progress"))
                    {
                        jobItem.Progress = Convert.ToDecimal(importJob["progress"]);
                    }

                    if (importJob.Attributes.Contains("data"))
                    {
                        jobItem.Status = GetStatus(importJob["data"].ToString());
                    }

                    jobItems.Add(jobItem);
                }
            }
            catch (Exception ex)
            {
                UpdateStripStatus(string.Format("Failed to retrieve results! ({0})", ex.Message));
                if (MessageBox.Show(ex.Message, "Error", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error) == DialogResult.Retry)
                {
                    LoadCurrentImportJob();
                }
            }
            CurrentJob = jobItems.FirstOrDefault();
        }
        #endregion

        #region Bg Worker Current Job Do Work
        /// <summary>
        /// Handles the DoWork event of the bgWorkerCurrentJob control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="System.ComponentModel.DoWorkEventArgs" /> instance containing the event data.
        /// </param>
        private void bgWorkerCurrentJob_DoWork(object sender, DoWorkEventArgs e)
        {
            LoadCurrentImportJob();
        }
        #endregion

        #region Bg Worker Current Job Run Worker Completed
        /// <summary>
        /// Handles the RunWorkerCompleted event of the bgWorkerCurrentJob control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="System.ComponentModel.RunWorkerCompletedEventArgs" /> instance containing the event data.
        /// </param>
        private void bgWorkerCurrentJob_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (CurrentJob == null)
            {
                UpdateStripStatus("Job not found !", 100);
                CanTryAgain(true);
                return;
            }

            UpdateStripStatus("Job found!", 100);
            CanTryAgain(false);
            propertyGridJob.SelectedObject = CurrentJob;
            BgWorkerCurrentJobProgress.DoWork += bgWorkerCurrentJobProgress_DoWork;
            BgWorkerCurrentJobProgress.RunWorkerCompleted += bgWorkerCurrentJobProgress_RunWorkerCompleted;
            if (!BgWorkerCurrentJobProgress.IsBusy)
            {
                BgWorkerCurrentJobProgress.RunWorkerAsync();
            }
        }
        #endregion

        #region Get Progress
        /// <summary>
        /// Gets the progress.
        /// </summary>
        /// <param name="importId">The import id.</param>
        private void GetProgress(Guid importId)
        {
            try
            {
                var connection = CrmConnection.Parse(GetCrmConnectionString());
                connection.Timeout = new TimeSpan(1, 0, 0);
                IOrganizationService service = new OrganizationService(connection);
                var job = service.Retrieve("importjob", importId, new ColumnSet(true));
                decimal progress = Convert.ToDecimal(job["progress"]);
                CurrentJob.Progress = progress;
                if (progress == 100)
                {
                    return;
                }
            }
            catch (Exception)
            {
                // Ignore
            }

            Thread.Sleep(Settings.Default.CheckInterval);
        }
        #endregion

        #region Bg Worker Current Job Progress Do Work
        /// <summary>
        /// Handles the DoWork event of the bgWorkerCurrentJobProgress control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="System.ComponentModel.DoWorkEventArgs" /> instance containing the event data.
        /// </param>
        private void bgWorkerCurrentJobProgress_DoWork(object sender, DoWorkEventArgs e)
        {
            GetProgress(CurrentJob.JobId);
        }
        #endregion

        #region Bg Worker Current Job Progress Run Worker Completed
        /// <summary>
        /// Handles the RunWorkerCompleted event of the bgWorkerCurrentJobProgress control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="System.ComponentModel.RunWorkerCompletedEventArgs" /> instance containing the event data.
        /// </param>
        private void bgWorkerCurrentJobProgress_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (CurrentJob == null)
            {
                return;
            }
            groupBoxProgress.Visible = true;
            progressBarJob.Value = (int)CurrentJob.Progress;
            toolStripProgressBar.Value = progressBarJob.Value;
            lblImportProgress.Text = string.Format("{0}%", CurrentJob.Progress.ToString("0.00"));
            lblImportStatus.Text = CurrentJob.Status;
            propertyGridJob.SelectedObject = CurrentJob;
            Application.DoEvents();

            if (!BgWorkerCurrentJobProgress.IsBusy)
            {
                BgWorkerCurrentJobProgress.RunWorkerAsync();
            }
        }
        #endregion

        #region Tab All Jobs Enter
        /// <summary>
        /// Handles the Enter event of the tabAllJobs control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="EventArgs" /> instance containing the event data.
        /// </param>
        private void tabAllJobs_Enter(object sender, EventArgs e)
        {
            BgWorkerJobs.DoWork += BgWorkerJobsOnDoWork;
            BgWorkerJobs.RunWorkerCompleted += BgWorkerJobsOnRunWorkerCompleted;
            if (!BgWorkerJobs.IsBusy)
            {
                UpdateStripStatus("Loading, please wait ...");
                BgWorkerJobs.RunWorkerAsync();
            }
        }
        #endregion

        #region Get All Jobs
        /// <summary>
        /// Gets all jobs.
        /// </summary>
        private void GetAllJobs()
        {
            string crmConnectionString = GetCrmConnectionString();
            if (string.IsNullOrEmpty(crmConnectionString))
            {
                return;
            }

            JobItems = new List<JobItem>();

            UpdateStripStatus("Connecting ...");
            var connection = CrmConnection.Parse(crmConnectionString);
            connection.Timeout = new TimeSpan(0, 5, 0);
            OrganizationService = new OrganizationService(connection);

            UpdateStripStatus("Querying for import jobs ...");
            var query = new QueryExpression("importjob") { ColumnSet = new ColumnSet(true) };

            try
            {
                var response = OrganizationService.RetrieveMultiple(query);
                UpdateStripStatus("Processing results ...");
                foreach (var importJob in response.Entities)
                {
                    var jobItem = new JobItem();
                    jobItem.JobId = importJob.Id;
                    jobItem.Name = importJob["name"].ToString();
                    if (importJob.Attributes.Contains("solutionname"))
                    {
                        jobItem.SolutionName = importJob["solutionname"].ToString();
                    }
                    if (importJob.Attributes.Contains("startedon"))
                    {
                        jobItem.CreatedOn = importJob["startedon"].ToString();
                    }
                    if (importJob.Attributes.Contains("completedon"))
                    {
                        jobItem.CompletedOn = importJob["completedon"].ToString();
                    }
                    if (importJob.Attributes.Contains("progress"))
                    {
                        jobItem.Progress = Convert.ToDecimal(importJob["progress"]);
                    }
                    if (importJob.Attributes.Contains("data"))
                    {
                        jobItem.Status = GetStatus(importJob["data"].ToString());
                    }

                    JobItems.Add(jobItem);
                }
            }
            catch (Exception ex)
            {
                UpdateStripStatus(string.Format("Failed to retrieve results! ({0})", ex.Message));
                if (MessageBox.Show(ex.Message, "Error", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error) == DialogResult.Retry)
                {
                    GetAllJobs();
                }
            }
        }
        #endregion

        #region Bg Worker Jobs On Do Work
        /// <summary>
        /// Bgs the worker jobs on do work.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="doWorkEventArgs">
        /// The <see cref="DoWorkEventArgs" /> instance containing the event data.
        /// </param>
        private void BgWorkerJobsOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
        {
            GetAllJobs();
        }
        #endregion

        #region Bg Worker Jobs On Run Worker Completed
        /// <summary>
        /// Bgs the worker jobs on run worker completed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="runWorkerCompletedEventArgs">
        /// The <see cref="RunWorkerCompletedEventArgs" /> instance containing the event data.
        /// </param>
        private void BgWorkerJobsOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs)
        {
            if (JobItems.Count > 0)
            {
                dgvJobs.DataSource = JobItems.OrderBy(j => j.CompletedOn).ToList();
            }
            UpdateStripStatus(string.Format("{0} job(s) loaded!", JobItems.Count), 100);
            CanTryAgain(JobItems.Count == 0);
        }
        #endregion

        #region Can Try Again
        /// <summary>
        /// Determines whether this instance [can try again] the specified can try again.
        /// </summary>
        /// <param name="canTryAgain">
        /// if set to <c>true</c> [can try again].
        /// </param>
        private void CanTryAgain(bool canTryAgain)
        {
            toolStripSplitButton.Visible = canTryAgain;
        }
        #endregion

        #region Tool Strip Split Button Button Click
        /// <summary>
        /// Handles the ButtonClick event of the toolStripSplitButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="EventArgs" /> instance containing the event data.
        /// </param>
        private void toolStripSplitButton_ButtonClick(object sender, EventArgs e)
        {
            CanTryAgain(false);
            if (tabControl.SelectedIndex == 0)
            {
                if (!BgWorkerCurrentJob.IsBusy)
                {
                    UpdateStripStatus("Loading, please wait ...");
                    BgWorkerCurrentJob.RunWorkerAsync();
                }
            }
            else if (tabControl.SelectedIndex == 1)
            {
                if (!BgWorkerJobs.IsBusy)
                {
                    UpdateStripStatus("Loading, please wait ...");
                    BgWorkerJobs.RunWorkerAsync();
                }
            }
        }
        #endregion

        #region Update Strip Status
        /// <summary>
        /// Updates the strip status.
        /// </summary>
        /// <param name="message">The message.</param>
        private void UpdateStripStatus(string message)
        {
            UpdateStripStatus(message, -1);
        }

        /// <summary>
        /// Updates the strip status.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="progress">The progress.</param>
        private void UpdateStripStatus(string message, int progress)
        {
            if (InvokeRequired)
            {
                // We're not in the UI thread, so we need to call BeginInvoke
                BeginInvoke(new UpdateStatusDelegate(UpdateStripStatus), new object[] { message, progress });
                return;
            }

            // Must be on the UI thread if we've got this far
            toolStripStatusLabel.Text = message;
            if (progress == -1)
            {
                toolStripProgressBar.Style = ProgressBarStyle.Marquee;
            }
            else
            {
                toolStripProgressBar.Style = ProgressBarStyle.Blocks;
                toolStripProgressBar.Value = progress;
            }
            Application.DoEvents();
        }
        #endregion

        #region Txt Org Url Text Changed
        /// <summary>
        /// Handles the TextChanged event of the txtOrgUrl control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="EventArgs" /> instance containing the event data.
        /// </param>
        private void txtOrgUrl_TextChanged(object sender, EventArgs e)
        {
            Settings.Default.OrgUrl = txtOrgUrl.Text;
            Settings.Default.Save();
        }
        #endregion

        #region Txt Domain Text Changed
        /// <summary>
        /// Handles the TextChanged event of the txtDomain control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="EventArgs" /> instance containing the event data.
        /// </param>
        private void txtDomain_TextChanged(object sender, EventArgs e)
        {
            Settings.Default.Domain = txtDomain.Text;
            Settings.Default.Save();
        }
        #endregion

        #region Txt Login Text Changed
        /// <summary>
        /// Handles the TextChanged event of the txtLogin control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="EventArgs" /> instance containing the event data.
        /// </param>
        private void txtLogin_TextChanged(object sender, EventArgs e)
        {
            Settings.Default.Login = txtLogin.Text;
            Settings.Default.Save();
        }
        #endregion

        #region Txt Password Text Changed
        /// <summary>
        /// Handles the TextChanged event of the txtPassword control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="EventArgs" /> instance containing the event data.
        /// </param>
        private void txtPassword_TextChanged(object sender, EventArgs e)
        {
            Settings.Default.Password = txtPassword.Text;
            Settings.Default.Save();
        }
        #endregion

        #region Txt Check Interval Text Changed
        /// <summary>
        /// Handles the TextChanged event of the txtCheckInterval control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="EventArgs" /> instance containing the event data.
        /// </param>
        private void txtCheckInterval_TextChanged(object sender, EventArgs e)
        {
            Settings.Default.CheckInterval = int.Parse(txtCheckInterval.Text);
            Settings.Default.Save();
        }
        #endregion
    }
}
